﻿using System;
using System.Collections.Generic;
using System.Linq;
using VirtualTournaments.Business.Tournaments;

namespace VirtualTournaments.Business.Bets
{
	/// <summary>
	/// Maneja las apuestas de una competencia
	/// Permite apostar, obtener las apuestas ganadoras, y obtener los objetivos a los que se puede apostar para ver las cuotas.
	/// Tiene una lista de los objetivos posibles con sus respectivas apuestas.
	/// </summary>
	public abstract class BetType : Observer.IObserver
	{
		ICompetition _competition;

		public ICompetition Competition
		{
			get { return _competition; }
		}

		public BetType(ICompetition competition)
		{
			_competition = competition;
			InitializeTargets(competition);
		}

		/// <summary>
		/// Inicializa los objetivos
		/// </summary>
		private void InitializeTargets(ICompetition competition)
		{
			if (this.BetTargets == null)
			{
				this.BetTargets = new List<BetTarget>();
				CreateAllAvaliableBetTargets(competition).ToList().ForEach(betTarget => AddBetTarget(betTarget));
			}
			UpdateOdds();
		}

		/// <summary>
		/// Crea los objetivos a los que se pueden apostar para esta competición.
		/// Template method que se llama en el constructor.
		/// </summary>
		/// <param name="competition"></param>
		/// <returns></returns>
		protected abstract BetTarget[] CreateAllAvaliableBetTargets(ICompetition competition);

		//este array se puede convertir en un hashmap para que sea mas performante
		/// <summary>
		/// Son todos los resultados apostables de esta competicion
		/// </summary>
		/// 
		IList<BetTarget> _betTargets = null;
		public virtual IList<BetTarget> BetTargets
		{
			get { return _betTargets; }
			protected set { _betTargets = value; }
		}

		protected void AddBetTarget(BetTarget betTarget)
		{
			BetTargets.Add(betTarget);
			betTarget.AddObserver(this);
		}

		public virtual IList<Bet> GetWinningBets()
		{
			if (!this.Competition.HasFinished())
			{
				throw new CompetitionNotFinishedException();
			}

			IList<Bet> winningBets = new List<Bet>();
			foreach (var anEvent in this.BetTargets)
			{
				if (anEvent.Occurs())
				{
					foreach (var winningBet in anEvent.Bets)
					{
						winningBets.Add(winningBet);
					}
				}
			}
			return winningBets;
		}

		/// <summary>
		/// Calcula cuanto paga cada objetivo. Si alguno de ellos no tiene apuestas se considera que paga la cantidad de apuestas que haya.
		/// </summary>
		protected virtual void UpdateOdds()
		{
			var totalWagered = (from target in BetTargets.AsQueryable()
								select target.Bets.Sum(bet => bet.WageredMoney)).Sum();
			foreach (var target in BetTargets)
			{
				var targetBets = (from bet in target.Bets
								  select bet.WageredMoney).Sum();

				var betCount = (from bettarget in this.BetTargets
								select bettarget.Bets.Count).Sum();
				if (betCount > 0)
				{
					var averageBet = totalWagered / betCount;
					try
					{
						target.SetOdds((totalWagered + averageBet) / (targetBets + averageBet));
					}
					catch (DivideByZeroException)
					{
						target.SetOdds(this.BetTargets.Count);
					}
				}
				else
				{
					target.SetOdds(this.BetTargets.Count);
				}
			}
		}

		#region IObserver Members
		/// <summary>
		/// Escucha cuando se agrega una apuesta a los resultados
		/// </summary>
		/// <param name="sender"></param>
		public void Update(VirtualTournaments.Business.Observer.Subject sender)
		{
			this.UpdateOdds();
		}

		#endregion
	}
}
